#pragma once
#include "CTokens.h"

class CMacroParam : public CIdentifier
{
	friend class CMacro;
	friend class CMacros;

	int		m_nIndex;
	bool	m_bIsVariadic;
	
	CMacroParam(const char * szName, int nIndex) :	CIdentifier(szName),
													m_nIndex(nIndex),
													m_bIsVariadic(false)
	{
	}

public:
	inline bool			IsVariadic() const					{return m_bIsVariadic;}
	inline void			IsVariadic(bool b)					{m_bIsVariadic = b;}
	inline int			GetIndex() const					{return m_nIndex;}
};

class CMacro : public CIdentifier
{
	friend class CMacros;

	CToken			*		m_pTokens[MAX_TOKENS];
	CLListEx<CMacroParam*>	m_Params;
	CLListEx<CToken*>		m_ArgList;

	int						m_nNumTokens;
	int						m_nNumParams;
	int						m_nSize;
	bool					m_bIsDeclared;			// only cant be while declaring
	bool					m_bHasVariadicParam;

	CMacro(const char *szName) :	CIdentifier(szName),
									m_nNumTokens(0),
									m_nNumParams(0),
									m_nSize(0),
									m_bIsDeclared(false),
									m_bHasVariadicParam(false),
									m_pInheritedByMacro(nullptr)
	{
	}

	~CMacro()
	{
		for(int i=0; i<MAX_TOKENS; i++)
			delete m_pTokens[i];
	}

public:
	CMacro		*	m_pInheritedByMacro;
	int				m_nInheritedMacroIdx;

	void AddToken(CToken *pToken)
	{
		if(m_nNumTokens > MAX_TOKENS)
			throw("Too many symbols in macro (split symbols between separate macros)");

		m_pTokens[m_nNumTokens++] = pToken;
	}

	CMacroParam * AddParam(const char *szName)
	{
		if(m_bHasVariadicParam)
			throw("Cannot have another parameter after the variable parameter");
		auto pMacroParam = new CMacroParam(szName, m_nNumParams++);
		if(pMacroParam->IsVariadic()) m_bHasVariadicParam = true;
		m_Params.Add(pMacroParam);
		return pMacroParam;
	}

	CToken	*	GetToken(int idx)
	{
		return idx < m_nNumTokens ? m_pTokens[idx] : nullptr;
	}

	CMacroParam * GetParam(const char *szName)
	{
		for(auto pLink = m_Params.Begin(); pLink != m_Params.Bad(); ++pLink)
		{
			if(pLink->IsNamed(szName))
				return *pLink;
		}
		return nullptr;
	}

	void GiveArg(CToken * pToken)
	{
		m_ArgList.Add(pToken);
	}
	CToken * TakeArg(CMacroParam * pParam)
	{
		int i = 0;
		for(auto pLink = m_ArgList.Begin(); pLink != m_ArgList.Bad(); ++pLink, ++i)
		{
			if(i == pParam->GetIndex()) return *pLink;
		}
		return nullptr;
	}

	inline int GetNumTokens() const			{return m_nNumTokens;}
	inline int GetNumParams() const			{return m_nNumParams;}

	inline bool IsDeclared() const				{return m_bIsDeclared;}
	inline void FinishDeclaring()				{m_bIsDeclared = true;}
};

class CMacros
{
	static CList<CMacro>		s_MacroList;
	static int					s_nNumMacros;

	CMacros()
	{
		Reset();
	}

	~CMacros()
	{
		s_MacroList.Clear();
	}

public:
	static CMacro	*	Add(const char *szName)
	{
		auto pMacro = new CMacro(szName);
		s_MacroList.Add(pMacro->GetHash(), pMacro);
		return pMacro;
	}

	static CMacro	*	Get(const char *szName)
	{
		DWORD hash = GenerateHash(szName);
		auto pMacro = s_MacroList.Get(hash);

		while(pMacro && !pMacro->IsNamed(szName, hash))
		{
			pMacro = s_MacroList.Next();
		}
		
		return pMacro;
	}

	static void			Remove(CMacro * pMacro)
	{
		s_MacroList.Drop(pMacro->GetHash(), pMacro);
	}

	static void Reset()
	{
		s_MacroList.Clear();
		s_nNumMacros = 0;
	}
};